#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <Arduino.h>
#include <IRremote.h>
#include "HackIR.h"

/* Required for the IRremote library */
IRsend transmitter;
IRrecv receiver(RX_PIN);
decode_results results;

/* Used for redirecting stdin/stdout/stderr to the USB UART */
FILE uartin = { 0 };
FILE uartout = { 0 };

/* List of supported IR encoding types and their human-readable descriptions */
struct supported_ir_types ir_types[] = {
	{ NEC, "NEC" },
	{ SONY, "SONY" },
	{ RC5, "RC5" },
	{ RC6, "RC6" },
	{ DISH, "DISH" },
	{ PANASONIC, "PANASONIC" },
	{ JVC, "JVC" },
	{ SANYO, "SANYO" },
	{ MITSUBISHI, "MITSUBISHI" },
	{ UNKNOWN, "UNKNOWN" }
};
#define TYPE_COUNT 10

/* Wrapper to tie stdout/stderr to the USB UART */
static int uart_putchar(char c, FILE *stream)
{
	if(c == '\n')
	{
		Serial.write('\r');
	}

	Serial.write(c);
	return 0;
}

/* Serial wrapper to tie stdin to the USB UART */
static int uart_getchar(FILE *stream)
{
	int c = -1;
	while((c = Serial.read()) == -1) { };
	return c;
}

/* Reads a line from stdin into the given buffer. Does not include the newline character. */
void readline(char *buf, int size)
{
	int i = 0, c = 0;

	for(i=0; i<(size-1); i++)
	{
		c = getchar();
		switch(c)
		{
			case '\r':
			case '\n':
			case '\0':
				i = MAX_LINE_SIZE;
				break;
			default:
				buf[i] = c;
				break;
		}
	}

	return;
}

/* Reads in the next IR code and populates the given ir_code struct accordingly */
void read_ir_code(struct ir_code *code)
{
	/* Re-start the receive timer before reading the next IR code */
	receiver.resume();

	/* Wait until we actually receive a valid IR code */
	while(!receiver.decode(&results)) { };

	code->type = results.decode_type;
	code->address = results.panasonicAddress;
	code->value = results.value;
	code->bits = results.bits;

	/* This only works for NEC; code->repeat is really only used for transmitting anyway */
	if(code->value == REPEAT)
	{
		code->repeat = 1;
	}
	else
	{
		code->repeat = 0;
	}

	return;
}

/* Transmits the specified IR code */
int write_ir_code(struct ir_code *code)
{
	int retval = 1;

	switch(code->type)
	{
		case NEC:
			transmitter.sendNEC(code->value, code->bits);
			break;
		case SONY:
			transmitter.sendSony(code->value, code->bits);
			break;
		case RC5:
			transmitter.sendRC5(code->value, code->bits);
			break;
		case RC6:
			transmitter.sendRC6(code->value, code->bits);
			break;
		case DISH:
			transmitter.sendDISH(code->value, code->bits);
			break;
		case PANASONIC:
			transmitter.sendPanasonic(code->address, code->value);
			break;
		case JVC:
			transmitter.sendJVC(code->value, code->bits, code->repeat);
			break;
		default:
			retval = 0;
			break;
	}

	/* Must re-enable the receiver after transmission! */
	receiver.enableIRIn();
	return retval;
}

/* Returns the IR encodning name associated with the specified type */
const char *name_by_type(uint8_t type)
{
	uint8_t i = 0;

	for(i=0; i<TYPE_COUNT; i++)
	{
		if(ir_types[i].type == type)
		{
			break;
		}
	}

	return ir_types[i].name;
}

/* Returns the IR encoding type associated with the specified name */
uint8_t type_by_name(const char *name)
{
	uint8_t i = 0;

	for(i=0; i<TYPE_COUNT; i++)
	{
		if(strcmp(name, ir_types[i].name) == 0)
		{
			break;
		}
	}

	return ir_types[i].type;
}

/*
 * Parses an IR code string and populates the given ir_code struct. Input format is:
 *
 * 	<IR type>:<IR code>:<# bits>:<repeat>:<address>
 */
void parse_ir_code_string(char *string, struct ir_code *code)
{
	unsigned long value = 0;
	char *code_ptr = NULL, *nbits_ptr = NULL, *repeat_ptr = NULL, *address_ptr = NULL;

	memset((void *) code, 0, sizeof(struct ir_code));

	if((code_ptr = strchr(string, DELIM)) != NULL)
	{
		code_ptr[0] = 0;
		code_ptr++;

		if((nbits_ptr = strchr(code_ptr, DELIM)) != NULL)
		{
			nbits_ptr[0] = 0;
			nbits_ptr++;

			if((repeat_ptr = strchr(nbits_ptr, DELIM)) != NULL)
			{
				repeat_ptr[0] = 0;
				repeat_ptr++;
				
				if((address_ptr = strchr(repeat_ptr, DELIM)) != NULL)
				{
					address_ptr[0] = 0;
					address_ptr++;

					code->address = strtoul(address_ptr, NULL, HEX);
				}

				code->repeat = atoi(repeat_ptr);
			}

			code->bits = atoi(nbits_ptr);
		}

		code->type = (int) type_by_name(string);
		code->value = strtoul(code_ptr, NULL, 16);
	}

	return;
}

/*
 * Prints the given IR code to stdout. Output format is:
 *
 * 	<IR type>:<IR code>:<# bits>:<repeat>:<address>
 */
void print_ir_code(struct ir_code *code)
{

	printf("%s%c0x%.8lX%c%d%c%d%c0x%.8X\n", name_by_type(code->type),
						DELIM,
						code->value,
						DELIM,
						code->bits,
						DELIM,
						code->repeat,
						DELIM,
						code->address);
	return;
}

/* Reads an IR code from the IR receiver and prints it to stdout */
void read_code(void)
{
	struct ir_code code = { 0 };

	read_ir_code(&code);
	print_ir_code(&code);
	
	return;
}

/* Reads in an IR code string from stdin and sends it to the IR transmitter */
void write_code(void)
{
	struct ir_code code = { 0 };
	char input[MAX_LINE_SIZE] = { 0 };

	readline((char *) &input, sizeof(input));
	parse_ir_code_string((char *) &input, &code);
	if(write_ir_code(&code))
	{
		printf("%s\n", ACK);
	}
	else
	{
		printf("%s\n", NACK);
	}
	
	return;
}

/* Initializes the necessary libraries */
void init_hackir(void)
{
	/* Initialize the IR receiver. */
	receiver.enableIRIn();

	/* Initialize the USB serial port and redirect stdin/stdout/stderr to the serial port */
	Serial.begin(BAUDRATE);
	fdev_setup_stream(&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
	fdev_setup_stream(&uartin, NULL, uart_getchar, _FDEV_SETUP_READ);
	stdout = &uartout;
	stderr = &uartout;
	stdin = &uartin;
}

int main(void)
{
	init_hardware();
	init_hackir();

	while(1)
	{
		switch(getchar())
		{
			case RECEIVE:
				read_code();
				break;
			case TRANSMIT:
				write_code();
				break;
			default:
				break;
		}
	}

	return 0;
}
